/**
 * 
 */
package easyservice.application;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.beanutils.MethodUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import easyservice.EasyServiceException;
import easyservice.network.ClientNetworkAgent;
import easyservice.network.XMPPClientNetworkAgent;
import easyservice.protocol.Command;
import easyservice.protocol.NotifyCommand;
import easyservice.protocol.RequestCommand;
import easyservice.protocol.ResponseCommand;

/**
 * @author Rodrigo Barbosa Lira
 *
 */
public class ClientAgentImpl<T, I> implements ClientAgent<T> {
	
	private static Log log = LogFactory.getLog(ClientAgentImpl.class);
	
	private ClientNetworkAgent networkAgent;
	
	private Class<T> facade;

	@SuppressWarnings("unused")
	private I notifiableInterface;
	
	public ClientAgentImpl(String username, String password, String hostUrl, int hostPort, String serverAlias, Class<T> facade, I notifiableInterface) {
		networkAgent = new XMPPClientNetworkAgent(username, password, hostUrl, hostPort, serverAlias, this);
		this.facade = facade;
		this.notifiableInterface = notifiableInterface;
	}

	/* (non-Javadoc)
	 * @see easyservice.network.NetworkListener#onReceiveMessage(easyservice.protocol.Command)
	 */
	@Override
	public Command onReceiveMessage(Command command) {
		// TODO review return type
		
		log.info("Retrieving command");
		NotifyCommand notify = (NotifyCommand) command;
		log.debug("Method: " + notify.getMethod());
		
		try {
			log.info("Executing notification");
			invokeMethod(notify);
		} catch (NoSuchMethodException e) {
			log.error("Method not found", e);
			throw new EasyServiceException(e);
		} catch (IllegalAccessException e) {
			log.error("Illegal Access", e);
			throw new EasyServiceException(e);
		} catch (InvocationTargetException e) {
			log.error("Illegal Access", e);
			throw new EasyServiceException(e);
		}
		
		log.info("Returning response. Actually always null.");
		return null;
	}

	/**
	 * @param notify
	 * @throws NoSuchMethodException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	private void invokeMethod(NotifyCommand notify) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
		// FIXME cast params to correct type
		MethodUtils.invokeMethod(notifiableInterface, notify.getMethod(), notify.getParams().toArray());
	}

	/* (non-Javadoc)
	 * @see easyservice.application.ClientAgent#getFacade()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public T getFacade() {
		Class[] interfaces = {this.facade};
		return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), interfaces, new ServerProxy(networkAgent));
	}

}
class ServerProxy implements InvocationHandler {
	
	private static Log log = LogFactory.getLog(ServerProxy.class);
	
	private ClientNetworkAgent networkAgent;
	
	public ServerProxy(ClientNetworkAgent networkAgent) {
		this.networkAgent = networkAgent;
	}

	/* (non-Javadoc)
	 * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// to request a response to server

		String nameOfMethod = method.getName();
		List<String> params = new ArrayList<String>();
		
		if (args != null) {
			for (Object o : args) {
				params.add(String.valueOf(o));
			}
		}
		
		Command command = new RequestCommand(nameOfMethod, params);
		
		log.info("Processing request");
		ResponseCommand result = (ResponseCommand) networkAgent.sendMessageToServer(command.generatedString(), 60000);
		log.debug("Result: " + result.getValue());
		
		Class returnType = method.getReturnType();
		log.debug("Return type: " + method.getReturnType().toString());
		
		if (returnType.equals(Character.class) || returnType.equals(char.class)) {
			log.debug("Return char result.");
			return (char) result.getValue().charAt(0);
		}
		
		if (returnType.equals(String.class)) {
			log.debug("Return string result.");
			return result.getValue();
		}
		
		if (returnType.equals(Integer.class) || returnType.equals(int.class)) {
			log.debug("Return int result.");
			return new Integer(result.getValue()).intValue();
		}
		
		if (returnType.equals(Long.class) || returnType.equals(long.class)) {
			log.debug("Return long result.");
			return new Long(result.getValue()).longValue();
		}
		
		if (returnType.equals(Float.class) || returnType.equals(float.class)) {
			log.debug("Return float result.");
			return new Float(result.getValue()).floatValue();
		}
		
		if (returnType.equals(Double.class) || returnType.equals(double.class)) {
			log.debug("Return double result.");
			return new Double(result.getValue()).doubleValue();
		}
		
		
		// return null in case of void return "type" 
		log.debug("Return null result.");
		return null;
	}
}